#include "vcache.h"

#include "../debug.h"

#ifdef WIN32
#include <windows.h>
#endif

#include "OpenGL.h"

e3dVertexCache::e3dVertexCache (e3dVertexBuffer* buf)
{
  Assert (buf != NULL, _T ("Invalid vertex cache!"));

  _buffer = buf;
}

void
e3dVertexBuffer::copy (const e3dVertexBuffer& buffer)
{
  // Must reallocate the buffer if the buffer to be coppied is
  // larger than the existing buffer.
  if (buffer.size () > size ())
    resize (buffer.size ());

  // Avoid memcpy, this needs to be an interface (so we can use VBOs)
  ////verts.clear ();
  ////verts = std::vector<e3dVertex>(buffer.verts.begin (), buffer.verts.end ());
  memcpy (&verts [0], &buffer.verts [0], sizeof (e3dVertex) * buffer.size ());

  update (0, verts.size ());
}

void
e3dVertexBuffer::destroy (void)
{
  verts.clear ();
}


void
e3dOpenGL::VertexBuffer::destroy (void)
{
  if (handle_ != 0) {
    glBindBuffer    (GL_ARRAY_BUFFER, 0);
    glDeleteBuffers (1, &handle_);
    handle_ = 0;
  }

  e3dVertexBuffer::destroy ();
}

void
e3dOpenGL::VertexBuffer::resize (size_t size)
{
  verts.resize (size);
}

void
e3dOpenGL::VertexBuffer::update (unsigned int first, unsigned int count)
{

  GLuint size_changed = first * sizeof (e3dVertex) + count * sizeof (e3dVertex);
  GLuint buffer_size  = verts.size () * sizeof (e3dVertex);

  /* Modified 5/27/2011 - Do not attempt to dereference the verts vector
                            unless it actually stores something... instead,
                              just pass the appropriate OpenGL function(s)
                                a NULL pointer.
  */

  if (handle_ == 0) {
    GLint bound;
    glGetIntegerv (GL_ARRAY_BUFFER_BINDING, &bound);

    glGenBuffers (1, &handle_);
    glBindBuffer ( GL_ARRAY_BUFFER, handle_ );
    glBufferData ( GL_ARRAY_BUFFER,
                      buffer_size,
                        verts.size () > 0 ? &verts [0] : NULL,
                            GL_STREAM_DRAW );
    GPU_size_ = verts.size () * sizeof (e3dVertex);

    /* Restore the originally bound buffer. */
    glBindBuffer (GL_ARRAY_BUFFER, bound);
  } else {
    /* Flag the Buffer Object as needing to be updated... */
    GPU_size_ = 0;

      /* The update will not occur until the buffer is actually used,
           this is a significant optimiation in situations where the
             buffer data is streamed sequentially. */
  }

//  printf ("OpenGL Vertex Buffer Update Invoked\n");
}

#include "rendercontext.h"

void
e3dOpenGL::VertexBuffer::bind (void)
{
  glBindBuffer (GL_ARRAY_BUFFER,         handle_);


  glVertexPointer (3, GL_FLOAT,         sizeof (e3dVertex), (float *)NULL + 0/*verts [0].pos*/);

  //if (flags & E3D_VB_NORMAL)
  glNormalPointer (GL_FLOAT, sizeof (e3dVertex), (float *)NULL + 3);

  // Tangent array
  glMultiTexCoordPointerEXT (GL_TEXTURE1_ARB, 3, GL_FLOAT, sizeof (e3dVertex), (float *)NULL + 6);

  //if (flags & E3D_VB_COLOR)
  glColorPointer  (4, GL_UNSIGNED_BYTE, sizeof (e3dVertex), (float *)NULL + 9/*&verts [0].color*/);
  //glColorPointer  (4, GL_UNSIGNED_BYTE, sizeof (e3dVertex), (float *)NULL + 3/*&verts [0].color*/);

  //if (flags & E3D_VB_FOG)
  // ...

//  if (flags & E3D_VB_TEXTURE) {
//    For Each Coordinate Set
//      Determine Texture Dimensions (1D, 2D or 3D)
//        glTexCoordPointer (<DIMENSIONS>, GL_FLOAT, sizeof (e3dVertex), verts->st);
//  }
  glMultiTexCoordPointerEXT (GL_TEXTURE0_ARB, 2, GL_FLOAT, sizeof (e3dVertex), (float *)NULL + 10/*&verts [0].st*/);

  //if (flags & E3D_VB_TANGENT) {
  //  glAttribPointer (...); // Tangent
  //  glAttribPointer (...); // Binormal
  //}

  //if (flags & E3D_VB_ATTRIB) {
  // ...
  //}
}

void
e3dOpenGL::VertexBuffer::unbind (void)
{
  glBindBuffer (GL_ARRAY_BUFFER, NULL);
}


void
e3dOpenGL::VertexBuffer::enable (void)
{
  glEnableClientState (GL_VERTEX_ARRAY);
  glEnableClientState (GL_COLOR_ARRAY);
  glEnableClientState (GL_NORMAL_ARRAY);

  // Tangent vector encoded into texunit1's coords...
  glClientActiveTexture (GL_TEXTURE1_ARB);
  glEnableClientState   (GL_TEXTURE_COORD_ARRAY);

  // Diffuse coords
  glClientActiveTexture (GL_TEXTURE0_ARB);
  glEnableClientState   (GL_TEXTURE_COORD_ARRAY);
}

void
e3dOpenGL::VertexBuffer::disable (void)
{
  // Tangent vector encoded into texunit1's coords...
  glClientActiveTexture (GL_TEXTURE1_ARB);
  glDisableClientState  (GL_TEXTURE_COORD_ARRAY);

  // Diffuse coords
  glClientActiveTexture (GL_TEXTURE0_ARB);
  glDisableClientState  (GL_TEXTURE_COORD_ARRAY);

  glDisableClientState (GL_NORMAL_ARRAY);
  glDisableClientState (GL_COLOR_ARRAY);
  glDisableClientState (GL_VERTEX_ARRAY);
}

void
e3dOpenGL::VertexBuffer::draw (e3dRenderStates::PrimitiveType primitive, unsigned int count)
{
  draw (primitive, 0, count);
}

void
e3dOpenGL::VertexBuffer::draw (e3dRenderStates::PrimitiveType primitive, int first, unsigned int count)
{
  using namespace e3dRenderStates;

  switch (primitive) {
    case Points:
      glDrawArrays (GL_POINTS, first, count);
      break;
    case Lines:
      glDrawArrays (GL_LINES, first, count);
      break;
    case Triangles:
      glDrawArrays (GL_TRIANGLES, first, count);
      break;
    case TriangleFan:
      glDrawArrays (GL_TRIANGLE_FAN, first, count);
      break;
    case TriangleStrip:
      glDrawArrays (GL_TRIANGLE_STRIP, first, count);
      break;
    case Quads:
      glDrawArrays (GL_QUADS, first, count);
      break;
  }
}

void
e3dOpenGL::VertexBuffer::draw_indexed (e3dRenderStates::PrimitiveType primitive, e3dIndexCache* indices, int first, unsigned int count)
{
/* TEMP HACK - I hope! */
//#ifdef WIN32
  //return;
//#endif

  if (GPU_size_ == 0) {
  /* Optimization, only upload new data when the size of the GPU data has changed,
       and do not do it immediately - instead the update occurs the next time the
         buffer is bound. */
    GPU_size_ = verts.size () * sizeof (e3dVertex);

    glBufferData ( GL_ARRAY_BUFFER,
                     GPU_size_,
                        &verts [0],
                          GL_STREAM_DRAW );
  }

  // This can be encapsulated...
  indices->Bind ();

  using namespace e3dRenderStates;

  switch (primitive) {
    case Points:
      glDrawElements (GL_POINTS, count, GL_UNSIGNED_SHORT, indices->GetIndexPointer ());
      break;
    case Lines:
      glDrawElements (GL_LINES, count, GL_UNSIGNED_SHORT, indices->GetIndexPointer ());
      break;
    case Triangles:
      glDrawElements (GL_TRIANGLES, count, GL_UNSIGNED_SHORT, indices->GetIndexPointer ());
      break;
    case TriangleFan:
      glDrawElements (GL_TRIANGLE_FAN, count, GL_UNSIGNED_SHORT, indices->GetIndexPointer ());
      break;
    case TriangleStrip:
      glDrawElements (GL_TRIANGLE_STRIP, count, GL_UNSIGNED_SHORT, indices->GetIndexPointer ());
      break;
    case Quads:
      glDrawElements (GL_QUADS, count, GL_UNSIGNED_SHORT, indices->GetIndexPointer ());
      break;
  }

  indices->Unbind ();
}














#ifdef HAS_D3D
void
e3dDirect3D::VertexBuffer::destroy (void)
{
  e3dVertexBuffer::destroy ();

  if (_vb != NULL) {
    _vb->Release ();
    _vb = NULL;
  }
}

void
e3dDirect3D::VertexBuffer::resize (size_t size)
{
  verts.resize (size);
}

void
e3dDirect3D::VertexBuffer::bind (void)
{
  const unsigned int num_verts = (unsigned int)size ();

  if (! num_verts) {
//    Assert (num_verts > 0, _T ("Attempting to bind an empty vertex buffer..."));
    return;
  }

  LPDIRECT3DDEVICE9 dev = GetDev ();

  if (! _vb) {
    dev->CreateVertexBuffer (num_verts * sizeof (e3dVertex),
                             D3DUSAGE_WRITEONLY, D3DFVF_VERTEX_BUFFER,
                             D3DPOOL_MANAGED, &_vb, NULL);

    void* dest;
    _vb->Lock (0, 0, &dest, num_verts * sizeof (e3dVertex));
      memcpy (dest, &verts [0], num_verts * sizeof (e3dVertex));
    _vb->Unlock ();
  }

  dev->SetStreamSource (0, _vb, 0, sizeof (e3dVertex));
}

void
e3dDirect3D::VertexBuffer::enable (void)
{
}

void
e3dDirect3D::VertexBuffer::disable (void)
{
}

void
e3dDirect3D::VertexBuffer::draw (e3dRenderStates::PrimitiveType primitive, unsigned int count)
{
  draw (primitive, 0, count);
}

void
e3dDirect3D::VertexBuffer::draw (e3dRenderStates::PrimitiveType primitive, int first, unsigned int count)
{
  using namespace e3dRenderStates;

  LPDIRECT3DDEVICE9 dev = GetDev ();
  
  switch (primitive)
  {
    case Points:
      dev->DrawPrimitive (D3DPT_POINTLIST, first, count);
      break;
    case Lines:
      dev->DrawPrimitive (D3DPT_LINELIST, first, count / 2);
      break;
    case Triangles:
      dev->DrawPrimitive (D3DPT_TRIANGLELIST, first, count / 3);
      break;
    case TriangleFan:
      dev->DrawPrimitive (D3DPT_TRIANGLEFAN, first, count - 2);
      break;
    case TriangleStrip:
      dev->DrawPrimitive (D3DPT_TRIANGLESTRIP, first, count - 2);
      break;
    case Quads:
      dev->DrawPrimitive (D3DPT_LINELIST, first, count / 2);
      break;
  }
}

void
e3dDirect3D::VertexBuffer::draw_indexed (e3dRenderStates::PrimitiveType primitive, e3dIndexCache* indices, int first, unsigned int count)
{
  // This can be encapsulated...
  indices->Bind ();

  LPDIRECT3DDEVICE9  dev       =                GetDev ();
  const unsigned int num_verts = (unsigned int) size   ();

  using namespace e3dRenderStates;

  switch (primitive)
  {
    case Points:
      dev->DrawIndexedPrimitive (D3DPT_POINTLIST, 0, 0, num_verts, first, count);
      break;
    case Lines:
      dev->DrawIndexedPrimitive (D3DPT_LINELIST, 0, 0, num_verts, first, count / 2);
      break;
    case Triangles:
      dev->DrawIndexedPrimitive (D3DPT_TRIANGLELIST, 0, 0, num_verts, first, count / 3);
      break;
    case TriangleFan:
      dev->DrawIndexedPrimitive (D3DPT_TRIANGLEFAN, 0, 0, num_verts, first, count - 2);
      break;
    case TriangleStrip:
      dev->DrawIndexedPrimitive (D3DPT_TRIANGLESTRIP, 0, 0, num_verts, first, count - 2);
      break;
    case Quads:
      dev->DrawIndexedPrimitive (D3DPT_LINELIST, 0, 0, num_verts, first, count / 2);
      break;
  }
}
#endif





e3dIndexCache::e3dIndexCache (e3dIndexBuffer* buf)
{
  Assert (buf != NULL, _T ("Invalid index cache!"));

  _buffer = buf;
}

void
e3dIndexBuffer::copy (const e3dIndexBuffer& buffer)
{
  // Must reallocate the buffer if the buffer to be coppied is
  // larger than the existing buffer.
  if (buffer.size () > size ())
    resize (buffer.size ());

  // Avoid memcpy, this needs to be an interface (so we can use VBOs)
  memcpy (&indices [0], &buffer.indices [0], sizeof (e3dIndex) * buffer.size ());
}

void
e3dIndexBuffer::destroy (void)
{
  indices.clear ();
}




void
e3dOpenGL::IndexBuffer::destroy (void)
{
  if (handle_ != 0) {
    glBindBuffer    (GL_ELEMENT_ARRAY_BUFFER, 0);
    glDeleteBuffers (1, &handle_);
    handle_ = 0;
  }

  e3dIndexBuffer::destroy ();
}

void
e3dOpenGL::IndexBuffer::resize (size_t size)
{
  indices.resize (size);
}

void
e3dOpenGL::IndexBuffer::update (unsigned int first, unsigned int count)
{
  GLuint size_changed = first * sizeof (e3dIndex) + count * sizeof (e3dIndex);
  GLuint buffer_size  = indices.size () * sizeof (e3dIndex);

  /* Modified 5/27/2011 - Do not attempt to dereference the indices vector
                            unless it actually stores something... instead,
                              just pass the appropriate OpenGL function(s)
                                a NULL pointer.
  */

  if (handle_ == 0) {
    GLint bound;
    glGetIntegerv (GL_ELEMENT_ARRAY_BUFFER_BINDING, &bound);

    glGenBuffers (1, &handle_);
    glBindBuffer ( GL_ELEMENT_ARRAY_BUFFER, handle_ );
    glBufferData ( GL_ELEMENT_ARRAY_BUFFER,
                     buffer_size,
                       indices.size () > 0 ? &indices [0] : NULL,
                          GL_STREAM_DRAW );
    GPU_size_ = indices.size () * sizeof (e3dIndex);

    /* Restore the originally bound buffer. */
    glBindBuffer (GL_ELEMENT_ARRAY_BUFFER, bound);
  } else {
    /* Flag the Buffer Object as needing to be updated... */
    GPU_size_ = 0;

      /* The update will not occur until the buffer is actually used,
           this is a significant optimiation in situations where the
             buffer data is streamed sequentially. */
  }
  
  //printf ("OpenGL Index Buffer Update Invoked\n");
}

void
e3dOpenGL::IndexBuffer::bind (void)
{
  if (! size ()) {
//    Assert (size (), _T ("Attempting to bind an empty index buffer..."));
    return;
  }

  glBindBuffer (GL_ELEMENT_ARRAY_BUFFER, handle_);

  if (GPU_size_ == 0) {
  /* Optimization, only upload new data when the size of the GPU data has changed,
       and do not do it immediately - instead the update occurs the next time the
         buffer is bound. */
    GPU_size_ = indices.size () * sizeof (e3dIndex);

    glBufferData ( GL_ELEMENT_ARRAY_BUFFER,
                     GPU_size_,
                        &indices [0],
                          GL_STREAM_DRAW );
  }
}

void
e3dOpenGL::IndexBuffer::unbind (void)
{
  glBindBuffer (GL_ELEMENT_ARRAY_BUFFER, NULL);
}

void
e3dOpenGL::IndexBuffer::enable (void)
{
}

void
e3dOpenGL::IndexBuffer::disable (void)
{
}



#ifdef HAS_D3D 
void
e3dDirect3D::IndexBuffer::destroy (void)
{
  e3dIndexBuffer::destroy ();

  if (_ib != NULL) {
    _ib->Release ();
    _ib = NULL;
  }
}

void
e3dDirect3D::IndexBuffer::resize (size_t size)
{
  indices.resize (size);
}

void
e3dDirect3D::IndexBuffer::bind (void)
{
  const unsigned int num_indices = (unsigned int)size ();

  if (! num_indices) {
//    Assert (num_indices > 0, _T ("Attempting to bind an empty index buffer..."));
    return;
  }

  LPDIRECT3DDEVICE9 dev = GetDev ();

  if (! _ib) {
    dev->CreateIndexBuffer (num_indices * sizeof (e3dIndex),
                            D3DUSAGE_WRITEONLY, D3DFMT_INDEX16,
                            D3DPOOL_MANAGED, &_ib, NULL);

    void* dest;
    _ib->Lock (0, 0, &dest, num_indices * sizeof (e3dIndex));
      memcpy (dest, &indices [0], num_indices * sizeof (e3dIndex));
    _ib->Unlock ();
  }

  dev->SetIndices (_ib);
  //dev->SetStreamSource (0, _ib, 0, sizeof (e3dIndex));
}

void
e3dDirect3D::IndexBuffer::enable (void)
{
}

void
e3dDirect3D::IndexBuffer::disable (void)
{
}
#endif /* HAS_D3D */
